#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c) 2022 Huawei Technologies Co.,Ltd.
#
# openGauss is licensed under Mulan PSL v2.
# You can use this software according to the terms
# and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#
#          http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
# ----------------------------------------------------------------------------
# Description  : cm_install is a utility to uninstall CM tool to openGauss database cluster.
#############################################################################

import os
import sys
import getopt
import subprocess
from CMLog import CMLog
from ErrorCode import ErrorCode
from InstallImpl import InstallImpl
from Common import *

class UnIntall:
    def __init__(self) -> None:
        self.envFile = ""
        self.xmlFile = ""
        self.bDeleteData = False
        self.bDeleteBinary = False
        self.hostnames = []
        self.cmDataPaths = []
        self.localhostName = getLocalhostName()

    def usage(self):
        """
cm_uninstall is a utility to uninstall CM tool to openGauss database cluster.

Usage:
    cm_uninstall -? | --help
    cm_uninstall -X XMLFILE [-e envFile] [--deleteData] [--deleteBinary]
General options:
    -X                                 Path of the XML configuration file.
    -e                                 Path of env file.
                                       Default value "~/.bashrc".
    --deleteData                       Specify to true if you want to remove cmdatapath and GAUSSLOG/cm.
    --deleteBinary                     Specify to true if you want to remove CM binary file.
    -?, --help                         Show help information for this
                                       utility, and exit the command line mode.
        """
        print(self.usage.__doc__)

    def parseCommandLine(self):
        if len(sys.argv) == 1:
            self.usage()
            sys.exit(1)

        try:
            opts, args = getopt.getopt(sys.argv[1:], "?X:e:",
                ["help", "deleteData", "deleteBinary"])
        except getopt.GetoptError as e:
            CMLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50000"] % str(e))

        for opt, value in opts:
            if opt in ("-?", "--help"):
                self.usage()
                sys.exit(0)
            elif opt in ("-X"):
                self.xmlFile = value
            elif opt in ("-e"):
                self.envFile = value
            elif opt in ("--deleteData"):
                self.bDeleteData = True
            elif opt in ("--deleteBinary"):
                self.bDeleteBinary = True

    def checkParam(self):
        if self.xmlFile == "":
            CMLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50001"] % 'X' + ".")
        checkXMLFile(self.xmlFile)

        if self.envFile == "":
            self.envFile = os.path.join(os.environ['HOME'], ".bashrc")
        if not os.path.exists(self.envFile):
             CMLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50201"] % ("envFile " + self.envFile))
        if not os.path.isfile(self.envFile):
            CMLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50210"] % ("envFile " + self.envFile))
        mppdbEnv = getEnvParam(self.envFile, "MPPDB_ENV_SEPARATE_PATH")
        if mppdbEnv != "":
            self.envFile = mppdbEnv
        if self.envFile == "" or not os.path.exists(self.envFile) or not os.path.isfile(self.envFile):
            CMLog.exitWithError(ErrorCode.GAUSS_518["GAUSS_51802"] % 'MPPDB_ENV_SEPARATE_PATH' + ".")

    def checkXMLContainCMInfo(self):
        cmd = "grep -E 'cmDir|cmsNum|cmServerPortBase|cmServerPortStandby|cmServerlevel|" \
            "cmServerListenIp1|cmServerRelation' " + self.xmlFile
        status, output = subprocess.getstatusoutput(cmd)
        if status == 0:
            CMLog.exitWithError("%s should not contain CM infos.\nDetail:\n%s" % (self.xmlFile, output))

    def getHostnames(self):
        """
        Get hostnames of all nodes from static file.
        """
        self.logger.debug("Getting hostnames")
        cmd = "source %s; cm_ctl view | grep 'nodeName'" % self.envFile
        status, output = subprocess.getstatusoutput(cmd)
        if status != 0:
            self.logger.logExit((ErrorCode.GAUSS_512["GAUSS_51203"] % "hostnames") + output)
        hostnames = output.split('\n')
        for i in range(0, len(hostnames)):
            hostname = hostnames[i].split(':')[1].strip()
            self.hostnames.append(hostname)
        self.logger.debug("hostnames=" + ','.join(self.hostnames))

    def getCMDataPaths(self):
        """
        Get cmDataPaths of all nodes from static file.
        """
        if not self.bDeleteData:
            return
        self.logger.debug("Getting CM datapaths.")
        cmd = "source %s; cm_ctl view | grep -E 'cmDataPath'" % self.envFile
        status, output = subprocess.getstatusoutput(cmd)
        if status != 0:
            self.logger.logExit((ErrorCode.GAUSS_512["GAUSS_51203"] % "cmDataPaths") + output)
        cmDataPaths = output.split('\n')
        for p in cmDataPaths:
            cmDataPath = p.split(':')[1].strip()
            self.cmDataPaths.append(cmDataPath)
        if self.cmDataPaths == []:
            self.logger.logExit("")
        self.logger.debug("cmDataPaths=" + ','.join(self.cmDataPaths))

    def cancleMonitorCrontab(self):
        """
        Cancle monitor crontab of all nodes.
        """
        self.logger.log("Cancling monitor crontab.")
        cronContentTmpFile = os.path.join("/tmp", "cronContentTmpFile_" + str(os.getpid()))
        cmd = """
            crontab -l > {cronContentTmpFile};
            sed '/.*om_monitor.*/d' {cronContentTmpFile} -i;
            crontab {cronContentTmpFile} && 
            rm -f {cronContentTmpFile}
            """.format(cronContentTmpFile=cronContentTmpFile)
        for host in self.hostnames:
            isLocal = False
            if host == self.localhostName:
                isLocal = True
            status, output = executeCmdOnHost(host, cmd, isLocal)
            if status != 0:
                errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" % (cmd, status, output)
                self.logger.logExit(("Failed to cancle monitor crontab on host %s." % host) + errorDetail)

    def stopCMProcess(self):
        """
        Stop CM process of all nodes, includeing om_monitor, cm_agent, cm_server, gaussdb fence
        """
        self.logger.log("Stopping CM process.")
        # When you execute the awk command through Python, sometimes the awk command is invalid,
        # just like your command does not use the awk command. Therefore, we have to avoid using awk.
        getPidsCmd = "ps -xo pid,command | grep -E 'om_monitor|cm_agent|cm_server|fenced UDF' | grep -v grep"
        for host in self.hostnames:
            isLocal = False
            if host == self.localhostName:
                isLocal = True
            # get CM process pids
            status, output = executeCmdOnHost(host, getPidsCmd, isLocal)
            self.logger.debug("Command: " + getPidsCmd)
            self.logger.debug("Status: " + str(status))
            self.logger.debug("Output: " + output)
            if output == "":
                continue
            processList = output.strip().split('\n')
            pidList = []
            for process in processList:
                pid = process.split()[0]
                if pid.isdigit():
                    pidList.append(pid)
            if pidList == []:
                continue
            # kill CM process
            pidsStr = ' '.join(pidList)
            killCMProcessCmd = "kill -9 " + pidsStr
            status, output = executeCmdOnHost(host, killCMProcessCmd, isLocal)
            if status != 0:
                errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" % (killCMProcessCmd, status, output)
                self.logger.logExit((ErrorCode.GAUSS_516["GAUSS_51606"] % "CM") + errorDetail)

    def refreshStaticAndDynamicFile(self):
        self.logger.log("Refreshing static and dynamic file using xml file without cm.")
        status, output  = InstallImpl.refreshStaticFile(self.envFile, self.xmlFile)
        if status != 0:
            self.logger.logExit("Failed to refresh static file." + output)
        # Remove dynamic file, if the cluster is stopped currently.
        removeDynamicCmd = "source %s; rm -f $GAUSSHOME/bin/cluster_dynamic_config" % self.envFile
        for host in self.hostnames:
            isLocal = False
            if host == self.localhostName:
                isLocal = True
            executeCmdOnHost(host, removeDynamicCmd, isLocal)
        clusterStopped = False
        checkClusterStoppedCmd = "source %s; ls $GAUSSHOME/bin/cluster_manual_start" % self.envFile
        status, output = subprocess.getstatusoutput(checkClusterStoppedCmd)
        if status == 0:
            clusterStopped = True
        self.logger.debug("Command: " + checkClusterStoppedCmd)
        self.logger.debug("Status: %s\nOtput: %s" % (status, output))
        if clusterStopped:
            return
        status, output = InstallImpl.refreshDynamicFile(self.envFile)
        if status != 0:
            if "The number of master dn must equal to 1." in output:
                self.logger.debug("Cann't refresh dynamic file when there is no normal primary in the current cluster.")
            else:
                self.logger.logExit("Failed to refresh dynamic file." + output)

    def deleteData(self):
        """
        remove cmdatapath
        """
        if not self.bDeleteData:
            return
        self.logger.log("Deleting CM data path.")
        self.logger.closeLog()
        for host, path in zip(self.hostnames, self.cmDataPaths):
            isLocal = False
            if host == self.localhostName:
                isLocal = True
            cmd = "source %s; rm -rf %s $GAUSSLOG/cm $GAUSSHOME/share/sslcert/cm" % (self.envFile, path)
            status, output = executeCmdOnHost(host, cmd, isLocal)
            if status != 0:
                errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" % (cmd, status, output)
                CMLog.exitWithError(("Failed to delete CM data path on host %s." % host) + errorDetail)

    def deleteBinary(self):
        """
        delete CM binaries, include om_monitor, cm_agent, cm_server, cm_ctl
        """
        if not self.bDeleteBinary:
            return
        self.logger.log("Deleting CM binaries.")
        for host in self.hostnames:
            isLocal = False
            if host == self.localhostName:
                isLocal = True
            cmd = "source %s; cd $GAUSSHOME/bin; rm -f om_monitor* cm_agent* cm_server* " \
                "cm_ctl* cm_persist* *manual*start* promote_mode_cms; cd -" % self.envFile
            status, output = executeCmdOnHost(host, cmd, isLocal)
            if status != 0:
                errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" % (cmd, status, output)
                self.logger.logExit(("Failed to delete CM binaries on host %s." % host) + errorDetail)

    def initLogger(self):
        gaussLog = getEnvParam(self.envFile, "GAUSSLOG")
        logPath = os.path.join(gaussLog, "cm", "cm_tool")
        if not os.path.exists(logPath):
            os.makedirs(logPath)
        self.logger = CMLog(logPath, module="cm_uninstall", prefix="cm_uninstall")

    def checkExeUser(self):
        if os.getuid() == 0:
            CMLog.exitWithError(ErrorCode.GAUSS_501["GAUSS_50105"])

    def checkHostTrust(self):
        checkHostsTrust(self.hostnames)

    def run(self):
        self.checkExeUser()
        self.parseCommandLine()
        self.checkParam()
        self.checkXMLContainCMInfo()
        self.initLogger()
        self.getHostnames()
        self.checkHostTrust()
        self.logger.log("Start to uninstall CM.")
        self.getCMDataPaths()
        self.cancleMonitorCrontab()
        self.stopCMProcess()
        self.refreshStaticAndDynamicFile()
        self.deleteBinary()
        self.deleteData()
        if self.bDeleteData:
            self.logger.printMessage("Uninstall CM tool success.")
        else:
            self.logger.logExit("Uninstall CM tool success.")

if __name__ == "__main__":
    unInstall = UnIntall()
    unInstall.run()
